<div class="container w1100 clearfloat">
  <div class="column mainCol l">
    <div id="content1" class="content">
      <h2 class="title">NodeJS基础</h2>
      <h2 class="subtitle">什么是NodeJS</h2>
      <p class="detialInfo">js是脚本语言，是需要一个解析器才能运行。写在html页面的js，浏览器充当了解析器，对于独立运行的js，node.js就是一个解析器。</p>
      <p class="detialInfo">每一种解析器都是一个运行环境，允许js定义各种数据结构，进行各种计算，还允许js使用运行环境提供的内置对象和方法做一些事情。eg：运行在浏览器中的js用途是操作DOM，浏览器就提供了document之类的内置对象，运行在node.js中的js用途是操作磁盘文件或搭建HTTP服务器，node.js就相应提供了fs、http等内置对象。</p>
      <h2 class="subtitle">模块</h2>
      <p class="detialInfo">在NodeJs中，一般将代码合理拆分都不同的js文件中，每一个文件就是一个模块，文件路径是模块名。编写每个模块时，有require、exports、module三个预先定义好的变量可供使用。</p>
      <p class="detialInfo">1、require函数用于在当前模块中加载和使用别的模块，传入一个模块名，返回一个模块导出对象。</p>
      <pre><code class="javascript">var foo1 = require("./foo");<br>var foo2 = require("./foo.js");<br>var foo3 = require("/home/user/foo");<br>/*foo1到foo3保存的是同一个模块的导出对象。可以使用一下加载方式使用JSON文件：*/<br>var data = require('./data.json');</code></pre>
      <p class="detialInfo">2、exports对象是当前模块的到处对象，用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时，得到的就是当前模块的exports对象，如下面的例子中导出了一个公有方法：</p>
      <pre><code class="javascript">exports.hello = function(){<br>&nbsp;console.log("hello world!");<br>};</code></pre>
      <p class="detialInfo">3、module对象可以访问到当前模块的一些相关信息，但最多的用途是替换当前模块的导出对象，例如：模块导出对象默认是一个普通对象，如果想改成一个函数的话，可以使用下面的例子</p>
      <pre><code class="javascript">module.exports= function(){<br>&nbsp;console.log("hello world!");<br>};/*模块默认 到处对象被替换为一个函数*/</code></pre>
      <p class="detialInfo">模块初始化——一个模块中的js代码仅在模块第一次被使用时执行一次，并在执行过程中初始化模块的导出对象。之后缓存起来的导出对象被重复利用。</p>
      <pre><code class="javascript">/*其中counter.js内容如下*/<br>var i = 0;<br>function count(){return ++i;}<br>exprots.count = count;<br><br>/*主模块main.js内容如下：*/var counter1 = require('./counter');<br>var counter2 = require('./counter');<br>console.log(counter1.count());/*1*/<br>console.log(counter2.count());/*2*/<br>console.log(counter2.count());/*3——可以看到counter.js并没有因为被require了两次而初始化两次*/</code></pre>

    </div>
    <div id="content2" class="content">
      <h2 class="title">node.js安装</h2>
      <h2 class="subtitle">window 7 64位下安装</h2>
      <p class="detialInfo">npm 作为一个nodejs的模块管理，我们要先配置npm的全局模块的存放路劲以及cache的路劲。希望将以上两个文件夹放在nodejs的主目录下，便在nodejs下建立“node_global”和“node_cache”两个文件夹。启动cmd，输入：</p>
      <pre><code class="javascript">npm config set prefix "D:\Program Files\nodejs\node_global"<br>npm config set cache "D:\Program Files\nodejs\node_cache"</code></pre>
      <p class="detialInfo">选择express这个比较常用的模块安装试试，同样在cmd里面输入“npm install express -g”（-g参数表示装到global目录下，也就是上面设置的D:\Program Files\nodejs\node_global里面。）cmd里面安装完了之后，会提示“express装在了哪、版本还有它的目录结构。</p>
      <p class="detialInfo">设置环境变量，进入环境变量对话框，在系统变量下新建“NODE_PATH”，输入“D:\Program Files\nodejs\node_global\node_modules”，用户变量里面的“PATH”修改为“D:\Program Files\nodejs\node_global”</p>
      <p class="detialInfo">以上都完成了，再次开启cmd，进入node，输入“require('express')来测试下node的模块全局路径是否配置正确。正确的话cmd会列出express的相关信心。</p>
      <h2 class="subtitle">安装cnpm</h2>
      <p class="detialInfo">在cmd中输入npm install -g cnpm --registry=http://registry.npm.taobao.org</p>
    </div>
    <div id="content3" class="content">
      <h2 class="title">安装vue.js</h2>
      <p class="detialInfo">上面的node.js和cnpm都安装成功了之后：</p>
      <ul class="infoList">
        <li>安装vue-cli脚手架构建工具，在cmd中运行命令cnpm install -g vue-cli，然后等待安装完成。</li>
        <li>用vue-cli构建项目，把cmd目录转换到选定的目录中，在cmd中运行命令vue init webpack firstVue——这个命令的意思是初始化一个项目，其中webpack是构建工具，也就是整个项目是基于webpack的。其中firstVue是整个项目文件夹的名称，这个文件夹会自动生成在你指定的目录中。运行初始化命令的时候会让用户输入几个基本的选项，如项目名称，描述，作者信息，如果不想写直接回车默认。</li>
        <li>安装项目中所需的依赖——要安装依赖包，先切换到项目文件夹（firstVue文件夹），然后运行命令 cnpm install。等待安装。</li>
        <li>运行项目，在项目目录中，运行命令npm run dev，会用热加载的方式运行我们的应用。热加载可以让我们在修改完代码后补用手动刷新浏览器就能实时看到修改后的效果。npm run dev命令其中run对应的是package.json文件中，scripts字段中的dev，也就是node build/dev-server.js。项目运行成功后，浏览器会自动打开localhost:8080，如果没有自动打开可手动输入。</li>
      </ul>
    </div>
    <div id="content4" class="content">
      <h2 class="title">代码的组织和部署</h2>
      <p class="detialInfo">使用NodeJs编写程序前，为了有个良好的开端，需要准备好代码的目录结构和部署方式。</p>
      <h2 class="subtitle">模块路径解析规则</h2>
      <p class="detialInfo">1、内置模块——如果传给require函数的是NodeJs内置模块名称，不做路径解析，直接返回内部模块的导出对象，如：require('fs');</p>
      <p class="detialInfo">2、node_modules目录——用于存放模块，例如某个模块的绝对路径是/home/user/hello.js，在该模块中使用require('foo/bar')方式加载模块时，则NodeJs一次尝试使用以下路径：</p>
      <pre><code class="javascript">/home/user/node_modules/foo/bar<br>/home/node_modules/foo/bar<br>/node_modules/foo/bar</code></pre>
      <p class="detialInfo">3、NODE_PATH环境变量——与PATH环境变量类似，可以包含一到多个目录路劲，NodeJs允许通过NODE_PATH环境变量来指定额外的模块搜索路劲。</p>
      <pre><code class="javascript">/*例如定义了以下NODE_PATH环境变量:<br>NODE_PATH=/home/user/lib:/home/lib<br>当使用require('foo/bar')的方式加载模块时，则NodeJs一尝试以下路劲：*/<br>/home/user/lib/foo/bar<br>/home/lib/foo/bar</code></pre>
      <h2 class="subtitle">包（package）</h2>
      <p class="detialInfo">我们把有多个子模块组成的大模块称做包，并把所有子模块放在同一个目录里。在组成一个包的所有子模块中，需要有一个入口模块，入口模块的导出对象被作为包的导出对象，例如以下目录结构：</p>
      <p class="detialInfo">-/home/user/lib/cat/——目录里有3个子模块（head.js、body.js、main.js），main.js作为入口模块，其内容如下：</p>
      <pre><code class="javascript">var head = require('./head');<br>var body = require('./body');<br>exports.create = function(name){<br>&nbsp;return{<br>&nbsp;&nbsp;name:name,<br>&nbsp;&nbsp;head:head.create(),<br>&nbsp;&nbsp;body:body.create()<br>&nbsp;};<br>};/*在其他模块里使用包的时候，需要加载包的入口模块——require('/home/user/lib/cat/main')*/</code></pre>
      <p class="detialInfo">1、index.js——模块的文件名是index.js，加载模块时可以使用模块所在目录的路径代替模块文件路径，以下两条语句等价：</p>
      <pre><code class="javascript">var cat = require('/home/user/lib/cat');<br>var cat = require('/home/user/lib/cat/index');<br>/*只需要把包目录路径传递给require函数，整个目录被当作单个模块使用，更有整体感。*/</code></pre>
      <p class="detialInfo">2、package.json——想自定义入口模块的文件名和存放位置，就需要在包目录下包含一个package.js文件，并在其中指定入口模块的路径，上面例中的cat模块可以重构如下：<br>-/home/user/lib/cat/——/lib/、packge.js。其中/lib/目录里包含head.js、body.js、main.js。package.json内容如下：</p>
      <pre><code class="javascript">{<br>&nbsp;"name":"cat",<br>&nbsp;"main":"./lib/main.js"<br>}/*同样可以使用require('/home/user/lib/cat')<br>的方式加载模块，NodeJs会根据包目录下的package.json找到入口模块所在位置*/</code></pre>
    </div>
    <div id="content5" class="content">
      <h2 class="title">文件操作</h2>
      <h2 class="subtitle">文件拷贝</h2>
      <p class="detialInfo">使用NodeJs内置的fs模块实现简单的文件拷贝程序:<br>1、小文件拷贝</p>
      <pre><code class="javascript">var fs = require('fs');<br>function copy(src,dst){<br>&nbsp;fs.writeFileSync(dst,fs,readFileSync(src));<br>}<br>function main(argv){<br>&nbsp;copy(argv[0],argv[1]);<br>}<br>main(process.argv.slice(2));<br></code></pre>
      <p class="detialInfo">2、大文件拷贝——对于大文件只能读一点写一点，直到完成拷贝。</p>
      <pre><code class="javascript">var fs = require('fs');<br>function copy(src,dst){<br>&nbsp;fs.createReadStream(src).pipe(fs.createWriteStream(dst));<br>}<br>function main(argv){<br>&nbsp;copy(argv[0],argv[1]);<br>}<br>main(process.argv.slice(2));<br>/*使用fs.createReadStream创建一个源文件的只读数据流，<br>并使用fs.createWriteStream创建了一个目标文件的只写数据流，用pipe方法把两个数据流连接了起来。*/</code></pre>
      <h2 class="subtitle"><a href="http://nodejs.cn/api/" target="__blank" style="color:#000;">Node API</a></h2>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/buffer.html" target="__blank">1、Buffer（数据块）</a></p>
      <p class="detialInfo">NodeJS提供了一个与String对等的全局构造函数Buffer来对二进制数据的操作，通过读取文件或直接构造来得到Buffer的实例。</p>
      <pre><code class="javascript">var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);<br>bin[0];/*0x68*/<br>var str = bin.toString('utf-8');/*hello*/<br>var bin = new Buffer('hello','utf-8');/*Buffer 68 65 6c 6c 6f */</code></pre>
      <p class="detialInfo">Buffer 与字符串的区别：字符串是只读，并且对字符串的任何修改得到的都是一个新字符串，原字符串保持不变。Buffer，更像是可以做指针操作的C语言数组，可以用[index]方式直接修改某个位置的字节。</p>
      <pre><code class="javascript">bin[0] = 0x48;</code></pre>
      <p class="detialInfo">而.slice方法也不是返回一个新的Buffer，更像是返回了指向原Buffer中间的某个位置的指针，因此对.slice方法返回的Buffer的修改会作用于原Buffer：</p>
      <pre><code class="javascript">var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);<br>var sub = bin.slice(2);<br>sub[0] = 0x65;<br>console.log(bin);/*Buffer 68 65 65 6c 6f */</code></pre>
      <p class="detialInfo">也因此，如果想要copy一份Buffer，要先创建一个新的Buffer，通过.copy方法把原Buffer中的数据复制过去。类似与申请一块新的内存，并把已有的内存中的数据复制过去，如下</p>
      <pre><code class="javascript">var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);<br>var dup = new Buffer(bin.length);<br>bin.copy(dup);<br>dup[0] = 0x48;<br>console.log(bin);/*Buffer 68 65 6c 6c 6f */<br>console.log(dup);/*Buffer 48 65 6c 6c 6f */</code></pre>
      <p class="detialInfo">Buffer将JS的数据处理能力从字符串扩展到了任意二进制数据。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/stream.html" target="__blank">2、Stream（数据流）</a></p>
      <p class="detialInfo">当内存中无法一次装下需要处理的数据时，或一边读取一边处理更加高效时，就需要用到数据流。NodeJs中通过各种Stream来提供对数据流的操作。以上面大文件copy程序为例，为数据来源创建一个只读数据流，如下：</p>
      <pre><code class="javascript">var rs = fs.createReadStream(pathname);<br>rs.on('data',function(chunk){<br>&nbsp;doSomething(chunk);<br>});<br>rs.on('end',function(){<br>&nbsp;clearUp();<br>});</code></pre>
      <p class="detialInfo">Stream基于事件机制工作，所有Stream的实例都是继承于NodeJS提供的<a class="link" href="http://nodejs.cn/api/events.html" target="__blank">EventEmitter</a>。上面的代码中data事件会不断地被触发，不管doSomething函数是否处理得过来，解决这个问题，如下：</p>
      <pre><code class="javascript">var rs = fs.createReadStream(pathname);<br>rs.on('data',function(chunk){<br>&nbsp;rs.pause();<br>&nbsp;doSomething(chunk,function(){<br>&nbsp;&nbsp;rs.resume();<br>&nbsp;});<br>});<br>rs.on('end',function(){<br>&nbsp;clearUp();<br>});</code></pre>
      <p class="detialInfo">上面代码给doSomething函数加上了回调，因此可以在处理数据前暂停数据读取，并在处理数据后继续读取数据。还可以为数据目标创建一个只写数据流，如下：</p>
      <pre><code class="javascript">var rs = fs.createReadStream(src);<br>var ws = createWriteStream(dst);<br>rs.on('data',function(chunk){<br>&nbsp;ws.write(chunk);<br>});<br>rs.on('end',function(){<br>&nbsp;ws.end();<br>});</code></pre>
      <p class="detialInfo">上面代码会出现这样的问题——如果写入速度跟不上读取速度的话，只写数据流内部的换成会爆仓。可以根据.write方法的返回值来判断传入的数据是写入目标了，还是临时放在了缓存，并根据drain事件来判断什么时候只写数据流已经将缓存中的数据写入目标，可以传入下一个代写数据了。改造代码如下：</p>
      <pre><code class="javascript">var rs = fs.createReadStream(src);<br>var ws = createWriteStream(dst);<br>rs.on('data',function(chunk){<br>&nbsp;if(ws.write(chunk) === false){rs.pause();}<br>});<br>rs.on('end',function(){<br>&nbsp;ws.end();<br>});<br>ws.on('drain',function(){<br>&nbsp;rs.resume();<br>});</code></pre>
      <p class="detialInfo">上面代码实现了数据从只读数据流到只写数据流的搬运，并包括了防爆仓控制，例如上面的大文件拷贝程序，NodeJS提供了.pipe的方法来实现，其内部实现方式与上面的代码类似。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/fs.html" target="__blank">3、File System（文件系统）</a></p>
      <p class="detialInfo">NodeJs通过fs内置模块提供对文件的操作，fs模块提供的AIP基本分为以下三类：</p>
      <ul class="infoList">
        <li>文件属性读写——常用的有：fs.stat、fs.chmod、fs.chown等。</li>
        <li>文件内容读写——常用的有：fs.readFile、fs.readdir、fs.writeFile、fs.mkdir等。</li>
        <li>底层文件操作——常用的有：fs.open、fs.read、fs.write、fs.close等。</li>
      </ul>
      <p class="detialInfo">NodeJS精华的异步IO模型在fs模块里有充分的体现，以fs.readFile为例：</p>
      <pre><code class="javascript">fs.readFile(pathname,function(err,data){<br>&nbsp;if(err){/*deal with error.*/}else{<br>&nbsp;/*deal with data.*/}<br>});</code></pre>
      <p class="detialInfo">基本上所有fs模块API的回调参数都有两个，第一个参数在有错误发生时，等于异常对象，第二个参数始终用于返回API方法执行结果。fs模块所有异步API都有对应的同步版本，同步API除了方法的末尾多了一个Sync之外，异常对象与执行结果的传递方式也有相应变化，如下：</p>
      <pre><code class="javascript">try{<br>&nbsp; var data = fs.readFileSync(pathname);<br>&nbsp;/*Deal with data.*/<br>catch(err){<br>&nbsp;/*deal with error.*/<br>}</code></pre>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/path.html" target="__blank">4、Path（路径）</a></p>
      <p class="detialInfo">1、path.normalize——将传入的路劲转换为标准路劲，如果程序需要使用路劲作为某些数据的索引，但又允许用户随意输入路劲时，则需要使用该方法保证路劲的唯一性：</p>
      <pre><code class="javascript">var cache = {};<br>function store(key,value){<br>&nbsp;cache[path.normalize(key)] = value;<br>}<br>store('foo/bar',1);<br>store('foo/baz//../bar',2);<br>console.log(cache);/*{"foo/bar":2}*/</code></pre>
      <p class="detialInfo">标准化之后的路径里斜杆在windows系统是\,在linux下是/。想要保证在任何系统下都使用/作为路劲分隔符的话，需要用.replace(/\\/g,'/')再替换下标准路劲。</p>
      <p class="detialInfo">2、path.join——将传入的多个路劲拼接为标准路劲，如下：</p>
      <pre><code class="javascript">path.join('foo/','baz/','../bar');/*"foo/bar"*/</code></pre>
      <h2 class="subtitle">遍历目录</h2>
      <p class="detialInfo">1、递归算法——通过不断缩小问题的规模来解决问题，如下：</p>
      <pre><code class="javascript">function factorial(n){<br>&nbsp;if(n === 1){<br>&nbsp;&nbsp;return 1;<br>&nbsp;}else{<br>&nbsp;&nbsp;return n*factorial(n - 1);<br>&nbsp;}<br>}</code></pre>      
      <p class="detialInfo">使用递归算法编写的代码虽然简洁，但由于每递归一次就产生一次函数调用，在需要优先考虑新能时，需要把递归算法转换为循环算法，以减少函数调用次数。</p>
      <p class="detialInfo">2、遍历算法——目录是一个树状结构，在遍历时一般使用深度优先+先序遍历算法。深度优先，意味着到达一个节点后，首先接着遍历子节点，而不是邻居节点。先序遍历，意味着首次到达了某节点就算遍历完成，而不是最后一次返回某节点才算数。</p>
      <p class="detialInfo">简单地实现目录遍历函数:<br>1、同步遍历</p>
      <pre><code class="javascript">function travel(dir,callback){<br>&nbsp;fs.readirSync(dir).forEach(function(file){<br>&nbsp;&nbsp;var pathname = path.join(dir,file);<br>&nbsp;&nbsp;if(fs.statSync(pathname).isDirectory()){<br>&nbsp;&nbsp;&nbsp;travel(pathname,callback);<br>&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;callback(pathname);<br>&nbsp;&nbsp;}<br>&nbsp;});<br>}<br><br>travel('/home/user',function(pathname){<br>&nbsp;console.log(pathname);<br>});</code></pre>      
      <p class="detialInfo">3、异步遍历</p>
      <pre><code class="javascript">function travel(dir,callback,finish){<br>&nbsp;fs.readdir(dir,function(err,files){<br>&nbsp;&nbsp;(function next(i){<br>&nbsp;&nbsp;&nbsp;var pathname = path.join(dir,files[i]);<br>&nbsp;&nbsp;&nbsp;fs.stat(pathname,function(err,stats){<br>&nbsp;&nbsp;&nbsp;&nbsp;if(stats.isDirectory()){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;travel(pathname,callback,function(){next(i + 1);});<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;callback(pathname,function(){next(i + 1);});<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;&nbsp;}else{finish && finish();}<br>&nbsp;&nbsp;}(0));<br>&nbsp;});<br>}</code></pre>      
      <h2 class="subtitle">文本编码</h2>
      <p class="detialInfo">1、BOM的移除——BOM用于标记一个文本文件使用Unicode编码吗，其本身是一个Unicode字符，位于文本文件头部，并不属于文件内容的一部分，因此一般使用NodeJs读取文本文件时，需要去掉DOM。如下：</p>
      <pre><code class="javascript">function readText(pathname){<br>&nbsp;var bin = fs.readFileSync(pathname);<br>&nbsp;if(bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF){<br>&nbsp;&nbsp;bin = bin.slice(3);<br>&nbsp;}<br>&nbsp;return bin.toString('utf-8');<br>}/*代码实现了识别和去除UTF8 BOM的功能。*/</code></pre>      
      <p class="detialInfo">2、BGK转UTF8——NodeJs支持在读取文本文件时，或者在Buffer转换为字符串时指定文本编码，但GBK编码不在NodeJs自身支持范围内。可以借组这个三方包来转换编码，如下：</p>
      <pre><code class="javascript">var iconv = require('iconv-lite');<br>function readGBKText(pathname){<br>&nbsp;var bin = fs.readFileSync(pathname);<br>&nbsp;return iconv.decode(bin,'gbk');<br>}</code></pre>      
    </div>
    <div id="content6" class="content">
      <h2 class="title">网络操作</h2>
      <p class="detialInfo">NodeJs本来的用途是编写高性能web服务器，使用NodeJs内置的http模块实现一个HTTP服务器：</p>
      <pre><code class="javascript">var http = require('http');<br>http.createServer(function(request,response){<br>&nbsp;response.writeHead(200,{'content-Type':'text-plain'});<br>&nbsp;response.end('hello world\n');<br>}).listen(8124);<br></code></pre>
      <p class="detialInfo">在Linux系统下，监听1024一下端口需要root权限，因此，如果想监听80或443端口的话，需要使用sudo命令启动程序。<br>在回调函数中，除了可以使用request对象访问请求头数据外，还能把request对象当作一个只读数据流来访问请求体数据，如下：</p>
      <pre><code class="javascript">http.createServer(function(request,response){<br>&nbsp;var body = [];<br>&nbsp;console.log(request.method);<br>&nbsp;console.log(request.headers);<br>&nbsp;request.on('data',function(chunk){<br>&nbsp;&nbsp;body.push(chunk);<br>&nbsp;});<br>&nbsp;request.on('end',function(){<br>&nbsp;&nbsp;body.Buffer.concat(body);<br>&nbsp;&nbsp;console.log(body.toString());<br>&nbsp;});<br>}).listen(80);</code></pre>
      <h2 class="subtitle">网络操作相关API</h2>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/http.html" target="__blank">1、HTTP </a></p>
      <p class="detialInfo">http模块有两种使用方式：<br>(1)、作为服务端使用时，创建一个HTTP服务器，监听HTTP客户端请求并返回响应。<br>(2)、作为客户端使用时，发起一个HTTP客户端请求，获取服务器端响应。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/https.html" target="__blank">2、HTTPS </a></p>
      <p class="detialInfo">https模块需要额外处理SSL证书，在服务端模式下，创建一个HTTPS服务器如下：</p>
      <pre><code class="javascript">var options = {<br>&nbsp;key:fs.readFileSync('./ssl/default.key'),<br>&nbsp;cert:fs.readFileSync('./ssl/default.cer')<br>};<br>var server = https.createServer(options,function(request,response){<br>&nbsp;/*...*/<br>});</code></pre>
      <p class="detailInfo">NodeJs支持SNI技术，可以根据HTTPS客户端请求使用的域名动态使用不同的证书，因此同一个HTTPS服务器可以使用多个域名提供服务。如下：</p>
      <pre><code class="javascript">server.addContext('foo.com'{<br>&nbsp;key:fs.readFileSync('./ssl/boo.com.key'),<br>&nbsp;cert:fs.readFileSync('./ssl/foo.com.cer')<br>});<br>server.addContext('bar.com',{<br>&nbsp;key:fs.readFileSync('./ssl/bar.com.key'),<br>&nbsp;cert:fs.readFileSync('./ssl/bar.com.cer')<br>});</code></pre>
      <p class="detailInfo">如果目标服务器使用的SSL证书是自制的，不是从颁发机构购买的，默认情况下HTTPS模块会拒绝连接，提示说证书安全问题，在options里加入rejectUnauthorized:false字段可以禁用对证书有效性的检查，从而允许https模块请求开发环境下只是自制证书的https服务器。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/url.html" target="__blank">3、URL </a><br>(1)、.parse方法将一个URL字符串转换为URL对象。<br>(2)、.format方法将一个URL对象转换为URL字符串。<br>(3)、.resolve方法用于拼接URL。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/querystring.html" target="__blank">4、Query String </a><br>querystring模块用于实现URL参数字符串与参数对象的互相转换。如下：</p>
      <pre><code class="javascript">querystring.parse('foo=bar&baz=qux&baz=quux&corge');<br>/*=>{foo:'bar', baz:['qux', 'quux'], corge:''}*/<br>querystring.stringify({foo:'bar',baz:['que','quux'],corge:''});<br>/*=>'foo=bar&baz=qux&baz=quux&corge='*/</code></pre>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/zlib.html" target="__blank">5、Zlib</a><br>zlib模块提供数据压缩和解压的功能，zlib模块压缩HTTP响应体数据这个过程中，先判断客户端是否支持gzip，支持的情况下使用zlib模块返回gzip之后的响应体数据，如下：</p>
      <pre><code class="javascript">http.createServer(function(request,response){<br>&nbsp;var i = 1024,data = '';<br>&nbsp;while(i--){<br>&nbsp;&nbsp;data += '.';<br>&nbsp;}<br>if((request.header['accept-encoding'] || '').indexOf('gzip') !== -1){<br>&nbsp;&nbsp;zlib.gzip(data,function(err,data){<br>&nbsp;&nbsp;&nbsp;response.writeHead(200,{<br>&nbsp;&nbsp;&nbsp;&nbsp;'Content-Type':'text/plain',<br>&nbsp;&nbsp;&nbsp;&nbsp;'Content-Encoding':'gzip'<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;response.writeHead(200,{<br>&nbsp;&nbsp;&nbsp;'Content-Type':'text/plain'<br>&nbsp;&nbsp;});<br>&nbsp;&nbsp;response.end(data);<br>&nbsp;}<br>}).listen(80);</code></pre>
      <p class="detialInfo">先判断服务端响应是否使用gzip压缩，然后使用zlib模块解压HTTP响应体数据，如下：</p>
      <pre><code class="javascript">var options = {<br>&nbsp;hostname:'www.example.com',<br>&nbsp;port:80,<br>&nbsp;path:'/',<br>&nbsp;method:'GET',<br>&nbsp;headers:{'Accept-Encoding':'gzip, deflate'}<br>};<br>http.request(options,function(response){<br>&nbsp;var body = [];<br>&nbsp;response.on('data',function(chunk){<br>&nbsp;&nbsp;body.push(chunk);<br>&nbsp;});<br>&nbsp;response.on('end',function(){<br>&nbsp;&nbsp;body = Buffer.concat(body);<br>&nbsp;&nbsp;if(response.headers['content-encoding'] === 'gzip'){<br>&nbsp;&nbsp;&nbsp;zlib.gunzip(body,function(err,data){<br>&nbsp;&nbsp;&nbsp;&nbsp;console.log(data.toString());<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;console.log(data.toString());<br>&nbsp;&nbsp;}<br>&nbsp;});<br>}).end();</code></pre>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/net.html" target="__blank">6、Net</a><br>net模块可用于创建Socket服务器或者Socket客户端。</p>
    </div>
    <div id="content7" class="content">
      <h2 class="title">进程管理</h2>
      <p class="detialInfo">NodeJs可以感知和控制自身进程的运行环境和状态，也可以创建子进程并与其协同工作，这使得NodeJs可以把多个程序组合在一起共同完成某项工作，并在其中充当胶水和调度器的作用。<br>如何使用NodeJs调用终端命令来简化拷贝，如下：</p>
      <pre><code class="javascript">var child_process = require('child_process');<br>var util = require('util');<br>function copy(source,tarrget,callback){<br>&nbsp;child_process.exec(<br>&nbsp;&nbsp;util.format('cp -r %s/* %s', source, target),callback);<br>}<br>copy('a','b',function(err){<br>&nbsp;/*...*/<br>});</code></pre>
      <p class="detialInfo">子进程是异步运行的，通过回调函数返回执行结果。</p>
      <h2 class="subtitle">进程管理API</h2>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/process.html" target="__blank">1、Process </a><br>在NodeJs中，可以通过process对象感知和控制NodeJs自身进程的方方面面，process不是内置模块，而是一个全局对象，在任何地方都可以直接使用。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/child_process.html" target="__blank">2、Child Process </a><br>使用child_process模块可以创建和控制子进程，该模块提供的API中最核心的是.spawn。</p>
      <p class="detialInfo"><a class="link" href="http://nodejs.cn/api/cluster.html" target="__blank">3、Cluster </a><br>cluster模块是对child_process模块的进一步封装，专用于解决单进程NodeJs Web服务器无法充分利用多核CPU的问题。使用该模块可以简化多进程服务器程序的开发，让每个核上运行一个工作进程，并统一通过主进程监听端口和分发请求。</p>
      <h2 class="subtitle">应用场景</h2>
      <p class="detialInfo">1、如何获取命令行参数:<br>NodeJs可以通过process.argv获取命令行参数，但node执行程序路劲和主模块文件路劲固定占据了argv[0]和argv[1]两个位置，而第一个命令参数从argv[2]开始，为了让argv使用起来更加自然，可以按照如下方式处理：</p>
      <pre><code class="javascript">function main(argv){/*...*/}<br>main(process.argv.slice(2));</code></pre>
      <p class="detialInfo">2、如何控制输入输出:<br>NodeJs程序的标准输入流（stdin）、标准输出流（stdout）、标准错误流（stderr）分别对应process.stdin、process.stdout、process.stderr。第一个是只读数据流，后面两个是只写数据流。console.log可以按照以下方式实现：</p>
      <pre><code class="javascript">function log(){<br>&nbsp;process.stdout.write(<br>&nbsp;&nbsp;util.format.apply(util,arguments)+'\n');<br>}</code></pre>
      <p class="detialInfo">3、如何降权:<br>在Linux系统下，需要使用root权限才能监听1024以下端口，但是一旦完成端口监听后，继续让程序运行在root权限下存在安全隐患，最好能把权限降下来。如下：</p>
      <pre><code class="javascript">http.createServer(callback).listen(80,function(){<br>&nbsp;var env = process.env,<br>&nbsp;&nbsp;uid = parseInt(env['SUDO_UID'] || process.getuid(),10),<br>&nbsp;&nbsp;gid = parseInt(env['SUDO_GID'] || process.getgid(),10);<br>&nbsp;process.setgid(gid);<br>&nbsp;process.setuid(uid);<br>});</code></pre>
      <p class="detialInfo">上例中有以下几点需要注意：<br>(1)、如果是通过sudo获取root权限的，运行程序的用户UID和GID保存在环境变量SUDO_DID和SUDO_GID里，如果是通过chmod+s方式获取root权限，运行程序的用户UID和GID可直接通过process.getuid和process.getgid方法获取。<br>(2)、process.setuid和process.setgid方法只接受number类型的参数。<br>(3)、降权时必须先降GID再降UID，否则顺序反过来的话就没有权限更改程序的GID了。</p>
      <p class="detialInfo">4、如何创建子进程:</p>
      <pre><code class="javascript">var child = child_process.spawn('node',['xxx.js']);<br>child.stdout.on('data',function(data){<br>&nbsp;console.log('stdout:'+data);<br>});<br>child.stder.on('data',function(data){<br>&nbsp;console.log('stderr:'+data);<br>});<br>child.on('close',function(code){<br>&nbsp;console.log('child process exited with code' + code);<br>});</code></pre>
      <p class="detialInfo">5、进程间如何通讯:<br>在Linux系统下，进程之间可以通过信号互相通信，如下；</p>
      <pre><code class="javascript">/*parent.js*/<br>var child = child_process.spawn('node',['child.js']);<br>child.kill('SIGTERM');<br>/*child.js*/<br>process.on('SIGTERM',function(){<br>&nbsp;cleanUp();<br>&nbsp;process.exit(0);<br>});</code></pre>
      <p class="detialInfo">在上例中，父进程通过.kill方法向子进程发送SIGTERM信号，子进程监听process对象的SIGTERM时间响应信号。.kill方法是用来给进程发送信号的，进程收到信号后具体做啥，完全取决与信号的种类和进程自身的代码。<br>如果父子进程都是NodeJs进程，就可以通过IPC（进程间通讯）双向传递数据。如下：</p>
      <pre><code class="javascript">/*parent.js*/<br>var child = child_process.spawn('node',['child.js'],{<br>&nbsp;stdio:[0,1,2,'ipc']<br>});<br>child.on('message',function(msg){<br>&nbsp;console.log(msg);<br>});<br>child.send({hello:'hello'});<br>/*child.js*/<br>process.on('message',function(msg){<br>&nbsp;msg.hello = msg.hello.toUpperCase();<br>&nbsp;process.send(msg);<br>});</code></pre>
      <p class="detialInfo">可以看到，父进程在创建子进程时，在options.stdio字段中通过ipc开启了一条IPC通道，之后就可以监听子进程对象的message事件接收来子进程的消息，并通过.send方法给子进程发送消息。在子进程这边，可以在process对象上监听message事件来接收来自父进程的消息，并通过.send方法向父进程发送消息。数据在传递过程中，会先在发送端使用JSON.stringify方法序列化，再接收端使用JSON.parse方法反序列化。</p>
      <p class="detialInfo">6、如何守护子进程:<br>守护进程一般用于监控工作进程的运行状态，在工作进程不正常退出时重启工作进程，保障工作进程不间断运行。如下：</p>
      <pre><code class="javascript">/*daemon.js*/<br>function spawn(mainModule){<br>&nbsp;var worker = child_process.spawn('node',[mainModule]);<br>&nbsp;worker.on('exit',function(code){<br>&nbsp;&nbsp;if(code !== 0){<br>&nbsp;&nbsp;&nbsp;spawn(mainModule);<br>&nbsp;&nbsp;}<br>&nbsp;});<br>}<br>spawn('worker.js');</code></pre>
      <p class="detialInfo">可以看到，工作进程非正常退出时，守护进程立即重启工作进程。<br>NodeJs管理进程时需要的API以及主要应用场景，总结起来有一下两点：<br>(1)、使用process对象管理自身。<br>(2)、使用child_process模块创建和管理子进程。</p>
    </div>
    <div id="content8" class="content">
      <h2 class="title">异步编程</h2>
      <h2 class="subtitle">回调</h2>
       <p class="detialInfo">在代码中，异步编程的直接体现是回调。异步编程依托于回调来实现，但不能说使用了回调后程序就异步化了，先看下面代码：</p>
       <pre><code class="javascript">function heavyCompute(n, callback) {<br>&nbsp;var count = 0, i, j;<br>&nbsp;for (i = n; i > 0; --i) {<br>&nbsp;&nbsp;for (j = n; j > 0; --j) {count += 1;}<br>&nbsp;}<br>&nbsp;callback(count);<br>}<br>heavyCompute(10000, function (count) {<br>&nbsp;console.log(count);<br>});<br>console.log('hello');<br>-- Console -----------------------------<br>100000000<br>hello</code></pre>
       <p class="detialInfo">以上代码中的回调函数仍然先于后续代码执行。JS本身是单线程运行的，不可能在一段代码还未结束运行时去运行别的代码，因此也就不存在异步执行的概念。但如果某个函数做的事情是创建一个别的线程或进程，并于JS主线程并行地做一些事情，并在事情做完后通知JS主线程，那情况有不一样了。如下：</p>
       <pre><code class="javascript">setTimeout(function () {<br>&nbsp;console.log('world');<br>}, 1000);<br>console.log('hello');<br>-- Console ------------------------------<br>hello<br>world</code></pre>
       <p class="detialInfo">现在是回调函数后于后续代码执行了。我们可以认为setTimeout这类JS规范之外的由运行环境提供的特殊函数做的事情是创建一个平行线程后立即返回，让JS主进程可以接着执行后续代码，并在收到平行进程的通知后再执行回调函数。除了setTimeout、setInterval这些常见的，这类函数还包括NodeJS提供的诸如fs.readFile之类的异步API。<br>我们仍然回到JS是单线程运行的这个事实上，这决定了JS在执行完一段代码之前无法执行包括回调函数在内的别的代码。也就是说，即使平行线程完成工作了，通知JS主线程执行回调函数了，回调函数也要等到JS主线程空闲时才能开始执行。如下：</p>
       <pre><code class="javascript">function heavyCompute(n) {<br>&nbsp;var count = 0, i, j;<br>&nbsp;for (i = n; i > 0; --i) {<br>&nbsp;&nbsp;for (j = n; j > 0; --j) {count += 1;}<br>&nbsp;}<br>}<br>var t = new Date();<br>setTimeout(function () {<br>&nbsp;console.log(new Date() - t);<br>}, 1000);<br><br>heavyCompute(50000);<br>-- Console -----------------------------<br>100000000<br>8520</code></pre>
       <p class="detialInfo">可以看到，本来应该在1秒后被调用的回调函数因为JS主线程忙于运行其它代码，实际执行时间被大幅延迟。</p>
       <h2 class="subtitle">代码设计模式</h2>
       <p class="detialInfo">1、函数返回值——使用一个函数的输出作为另一个函数的输入。<br>在同步方式下一般按以下方式编写代码：</p>
       <pre><code class="javascript">var output = fn1(fn2('input'));<br>// Do something.</code></pre>
       <p class="detialInfo">而在异步方式下，由于函数执行结果不是通过返回值，而是通过回调函数传递，因此一般按以下方式编写代码：</p>
       <pre><code class="javascript">fn2('input', function (output2) {<br>&nbsp;fn1(output2, function (output1) {<br>&nbsp;&nbsp;/* Do something.*/<br>&nbsp;});<br>});</code></pre>
       <p class="detialInfo">这种方式就是一个回调函数套一个回调函</p>
       <p class="detialInfo">2、遍历数组——如果函数是同步执行的，一般就会写出如下代码：</p>
       <pre><code class="javascript">var len = arr.length,i = 0;<br>for (; i < len; ++i) {<br>&nbsp;arr[i] = sync(arr[i]);<br>}<br>// All array items have processed.</code></pre>
       <p class="detialInfo">如果函数是异步执行的，以上代码就无法保证循环结束后所有数组成员都处理完毕了。如果数组成员必须一个接一个串行处理，则一般按照以下方式编写异步代码：</p>
       <pre><code class="javascript">(function next(i,len,callback){<br>&nbsp;if(i < len){<br>&nbsp;&nbsp;async(arr[i],function(value){<br>&nbsp;&nbsp;&nbsp;arr[i] = value;<br>&nbsp;&nbsp;&nbsp;next(i + 1,len,callback);<br>&nbsp;&nbsp;});<br>&nbsp;}else{<br>&nbsp;&nbsp;callback();<br>&nbsp;}<br>}(0,arr.length,function(){<br>&nbsp;/*All array items have processed.*/<br>}));</code></pre>
       <p class="detialInfo">以上代码在异步函数执行一次并返回执行结果后才传入下一个数组成员并开始下一轮执行，直到所有数组成员处理完毕后，通过回调的方式触发后续代码的执行。<br>如果数组成员可以并行处理，但后续代码仍然需要所有数组成员处理完毕后才能执行的话，则异步代码会调整成以下形式:</p>
       <pre><code class="javascript">(function (i, len, count, callback){<br>&nbsp;for(; i < len; i++){<br>&nbsp;&nbsp;(function(i){<br>&nbsp;&nbsp;&nbsp;async(arr[i],function(value){<br>&nbsp;&nbsp;&nbsp;&nbsp;arr[i] = value;<br>&nbsp;&nbsp;&nbsp;&nbsp;if(++count === len){callback();}<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}(i));<br>&nbsp;}<br>}(0,arr.length,0,function(){<br>&nbsp;/*All array items have processed.*/<br>}));</code></pre>
       <p class="detialInfo">与异步串行遍历的版本相比，以上代码并行处理所有数组成员，并通过计数器变量来判断什么时候所有数组成员都处理完毕了。</p>
       <p class="detialInfo">3、异常处理——JS自身提供的异常捕获和处理机制 try...catch...，只能用于同步执行的代码，如下；</p>
       <pre><code class="javascript">function sync(fn) {return fn();}<br>try{<br>&nbsp;sync(null);<br>&nbsp; /*Do something*/<br>}catch (err){<br>&nbsp;console.log('Error: %s', err.message);<br>}<br>-- Console ------------------------------<br>Error: object is not a function</code></pre>
       <p class="detialInfo">可以看到，异常会沿着代码执行路径一直冒泡，直到遇到第一个try语句时，被捕获住，但由于异步函数会打断代码执行路径，异步函数执行过程中以执行之后产生的异常冒泡到执行路劲被打断的位置时，如果一直没有遇到try语句，就作为一个全局异常抛出，如下：</p>
       <pre><code class="javascript">function async(fn,callback){<br>&nbsp;/*Code execution path breaks here.*/<br>&nbsp;setTimeout(function(){<br>&nbsp;&nbsp;callback(fn());<br>&nbsp;},0);<br>}<br>try{<br>&nbsp;async(null,function(data){<br>&nbsp;&nbsp;/*Do something.*/<br>&nbsp;});<br>}catch(err){<br>&nbsp;console.log('error:%s',err.message);<br>}<br>-- Console -----------------------------<br>/home/user/test.js:4<br>callback(fn());<br>TypeError: object is not a function<br>at null._onTimeout (/home/user/test.js:4:13)<br>at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)</code></pre>
       <p class="detialInfo">因为代码执行路劲被打断，我们就需要在异常冒泡到断点之前用try语句把异常捕获住，并通过回调函数传递被捕获的异常，我们可以像下边这样改造上面的例子：</p>
       <pre><code class="javascript">function async(fn, callback) {<br>&nbsp;/*Code execution path breaks here.*/<br>&nbsp;setTimeout(function (){<br>&nbsp;&nbsp;try{<br>&nbsp;&nbsp;&nbsp;callback(null,fn());<br>&nbsp;&nbsp;catch(err){<br>&nbsp;&nbsp;&nbsp;callback(err);<br>&nbsp;&nbsp;}<br>&nbsp;},0);<br>}<br>async(null,function(err,data){<br>&nbsp;if(err){<br>&nbsp;&nbsp;console.log('error:%s',err.message);<br>&nbsp;}else{/* Do something.*/}<br>});<br>-- Console ------------------------------<br>Error: object is not a function</code></pre>
       <p class="detialInfo">回调函数已经让代码变得复杂了，而异步方式下对异常的处理更加剧了代码的复杂度，下面介绍NodeJs提供的一些解决方案。</p>
       <h2 class="subtitle"><a href="http://nodejs.cn/api/domain.html" target="__blank" style="color:#000;">域</a></h2>
       <p class="detialInfo">NodeJS提供了domain模块，可以简化异步代码的异常处理，一个域就是一个js运行环境，在一个运行环境中，如果一个异常乜嘢被捕获，将作为一个全局异常被抛出，NodeJs通过process对象提供了捕获全局异常的方法，如下：</p>
       <pre><code class="javascript">process.on('uncaughtException',function(err){<br>&nbsp;console.log('Error:%s',err.message);<br>});<br>setTimeout(function(fn){fn();});<br>-- Console ------------------------------<br>Error: undefined is not a function</code></pre>
       <p class="detialInfo">虽然全局异常有个地方可以捕获了，但对于大多数异常，我们希望尽早捕获，并根据结果决定代码的执行路径，如下HTTP服务器作为例子：</p>
       <pre><code class="javascript">function async(request, callback){<br>&nbsp;/*Do something*/<br>&nbsp;asyncA(request, function(err, data){<br>&nbsp;&nbsp;if(err){callback(err);}else{<br>&nbsp;&nbsp;/*Do something*/<br>&nbsp;&nbsp;asyncB(request, function(err, data){<br>&nbsp;&nbsp;&nbsp;if(err){callback(err);}else{<br>&nbsp;&nbsp;&nbsp;/*Do something*/<br>&nbsp;&nbsp;&nbsp;asyncC(request, function(err,data){<br>&nbsp;&nbsp;&nbsp;&nbsp;if(err){callback(err);}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*Do something*/<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;callback(null, data);}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}<br>&nbsp;});<br>}<br>http.createServer(function(request,response){<br>&nbsp;async(request, function(err, data){<br>&nbsp;&nbsp;if(err){<br>&nbsp;&nbsp;&nbsp;response.writeHead(500);<br>&nbsp;&nbsp;&nbsp;response.end();<br>&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;response.writeHead(200);<br>&nbsp;&nbsp;&nbsp;response.end(data);<br>&nbsp;&nbsp;}<br>&nbsp;});<br>});</code></pre>
       <p class="detialInfo">可以在每处理一个请求时，使用domain模块创建一个子域，在子域内运行的代码可以随意抛出异常，而这些异常可以通过子域对象的error事件同意捕获，于是上面的代码可以做如下改造：</p>
       <pre><code class="javascript">function async(request, callback){<br>&nbsp;/*Do something*/<br>&nbsp;asyncA(request, function(data){<br>&nbsp;&nbsp;/*Do something*/<br>&nbsp;&nbsp;asyncB(request, function(data){<br>&nbsp;&nbsp;&nbsp;/*Do something*/<br>&nbsp;&nbsp;&nbsp;asyncC(request, function(data){<br>&nbsp;&nbsp;&nbsp;&nbsp;/*Do something*/<br>&nbsp;&nbsp;&nbsp;&nbsp;callback(data);<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;});<br>&nbsp;});<br>}<br>http.createServer(function(request, response){<br>&nbsp;var d = domain.create();<br>&nbsp;d.on('error', function(){<br>&nbsp;&nbsp;response.writeHead(500);<br>&nbsp;&nbsp;response.end();<br>&nbsp;});<br>&nbsp;d.run(function(){<br>&nbsp;&nbsp;async(request, function(data){<br>&nbsp;&nbsp;&nbsp;response.writeHead(200);<br>&nbsp;&nbsp;&nbsp;response.end(data);<br>&nbsp;&nbsp;});<br>&nbsp;});<br>});</code></pre>
       <p class="detialInfo">陷阱——无论是通过process对象的uncaughtException事件捕获到全局异常，还是通过子域对象的error事件捕获到了子域异常，在NodeJS官方文档里都强烈建议处理完异常后立即重启程序，而不是让程序继续运行。按照官方文档的说法，发生异常后的程序处于一个不确定的运行状态，如果不立即退出的话，程序可能会发生严重内存泄漏，也可能表现得很奇怪。<br>这里需要澄清一些事实。JS本身的throw..try..catch异常处理机制并不会导致内存泄漏，也不会让程序的执行结果出乎意料，但NodeJS并不是存粹的JS。NodeJS里大量的API内部是用C/C++实现的，因此NodeJS程序的运行过程中，代码执行路径穿梭于JS引擎内部和外部，而JS的异常抛出机制可能会打断正常的代码执行流程，导致C/C++部分的代码表现异常，进而导致内存泄漏等问题。因此，使用uncaughtException或domain捕获异常，代码执行路径里涉及到了C/C++部分的代码时，如果不能确定是否会导致内存泄漏等问题，最好在处理完异常后重启程序比较妥当。而使用try语句捕获异常时一般捕获到的都是JS本身的异常，不用担心上诉问题。</p>
    </div>
    <div id="content9" class="content">
      <h2 class="title">大示例</h2>
      <p class="detialInfo">完整地介绍一个使用NodeJS开发Web服务器的示例：</p>
      <h2 class="subtitle">需求</h2>
      <p class="detialInfo">我们要开发的是一个简单的静态文件合并服务器，该服务器需要支持类似以下格式的JS或CSS文件合并请求。</p>
      <pre><code class="html">http://assets.example.com/foo/??bar.js,baz.js</code></pre>
      <p class="detialInfo">在以上URL中，??是一个分隔符，之前是需要合并的多个文件的URL的公共部分，之后是使用,分隔的差异部分。因此服务器处理这个URL时，返回的是以下两个文件按顺序合并后的内容。</p>
      <pre><code class="javascript">/foo/bar.js<br>/foo/baz.js</code></pre>
      <p class="detialInfo">另外，服务器也需要能支持类似以下格式的普通的JS或CSS文件请求。</p>
      <pre><code class="html">http://assets.example.com/foo/bar.js</code></pre>
      <h2 class="subtitle">第一次迭代</h2>
      <p class="detialInfo">快速迭代是一种不错的开发方式，因此我们在第一次迭代时先实现服务器的基本功能。</p>
      <p class="detialInfo">1、设计——服务器会首先分析URL，得到请求的文件的路径和类型（MIME）。然后，服务器会读取请求的文件，并按顺序合并文件内容。最后，服务器返回响应，完成对一次请求的处理。另外，服务器在读取文件时需要有个根目录，并且服务器监听的HTTP端口最好也不要写死在代码里，因此服务器需要是可配置的。</p>
      <p class="detialInfo">2、实现——根据以上设计，我们写出了第一版代码如下:</p>
      <pre><code class="javascript">var fs = require('fs'),path = require('path'),http = require('http');<br>var MIME = {'.css':'text/css','.js':'application/javascript'};<br>function combineFiles(pathnames,callback){<br>&nbsp;var output = [];<br>&nbsp;(function next(i,len){<br>&nbsp;&nbsp;if(i < len){<br>&nbsp;&nbsp;&nbsp;fs.readFile(pathnames[i],function(err,data){<br>&nbsp;&nbsp;&nbsp;&nbsp;if(err){callback(err);}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output.push(data);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;next(i + 1,len);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;callback(null,Buffer.concat(output));<br>&nbsp;&nbsp;}<br>&nbsp;}(0,pathname.length));<br>}<br><br>function main(argv){<br>&nbsp;var config = JSON.parse(fs.readFileSync(argv[0],'utf-8')),<br>&nbsp;root = config.root || '.',<br>&nbsp;port = config.port || 80;<br>&nbsp;http.createServer(function(request,response){<br>&nbsp;&nbsp;var urlInfo = parseURL(root,request.url);<br>&nbsp;&nbsp;combineFiles(urlInfo.pathnames,function(err,data){<br>&nbsp;&nbsp;&nbsp;if(err){<br>&nbsp;&nbsp;&nbsp;&nbsp;response.ws.writeHead(404);<br>&nbsp;&nbsp;&nbsp;&nbsp;response.end(err.message);<br>&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;response.writeHead(200,{'Content-Type':urlInfo.mine});<br>&nbsp;&nbsp;&nbsp;&nbsp;response.end(data);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;});<br>&nbsp;}).listen(port);<br>}<br><br>function parseURL(root,url){<br>&nbsp;var base,pathnames,parts;<br>&nbsp;if(url.indexOf('??') === -1){<br>&nbsp;&nbsp;url = url.replace('/','/??');<br>&nbsp;}<br>&nbsp;parts = url.split('??');<br>&nbsp;base = parts[0];<br>&nbsp;pathnames = parts[1].split(',').map(function(value){<br>&nbsp;&nbsp;return path.join(root,base,value);<br>&nbsp;});<br>&nbsp;return {<br>&nbsp;&nbsp;mine:MIME[path.extname(pathname[0])] || 'text/plain',<br>&nbsp;&nbsp;pathnames:pathnames<br>&nbsp;};<br>}<br><br>main(process.argv.slice(2));</code></pre>
      <p class="detialInfo">我们可以把以上代码保存为server.js，之后就可以通过node server.js config.json命令启动程序，于是我们的第一版静态文件合并服务器就顺利完工了。另外，以上代码存在一个不那么明显的逻辑缺陷。例如，使用以下URL请求服务器时会有惊喜。</p>
      <pre><code class="html"> http://assets.example.com/foo/bar.js,foo/baz.js</code></pre>
      <p class="detialInfo">经过分析之后我们会发现问题出在/被自动替换/??这个行为上，而这个问题我们可以到第二次迭代时再解决。</p>
      <h2 class="subtitle">第二次迭代</h2>
      <p class="detialInfo">在第一次迭代之后，我们已经有了一个可工作的版本，满足了功能需求。接下来我们需要从性能的角度出发，看看代码还有哪些改进余地。</p>
      <p class="detialInfo">1、设计——把map方法换成for循环或许会更快一些，但第一版代码最大的性能问题存在于从读取文件到输出响应的过程当中。第一版代码依次把请求的文件读取到内存中之后，再合并数据和输出响应。这会导致以下两个问题：<br>(1)、当请求的文件比较多比较大时，串行读取文件会比较耗时，从而拉长了服务端响应等待时间。<br>(2)、由于每次响应输出的数据都需要先完整地缓存在内存里，当服务器请求并发数较大时，会有较大的内存开销。</p>
      <p class="detialInfo">对于第一个问题，很容易想到把读取文件的方式从串行改为并行。但是别这样做，因为对于机械磁盘而言，因为只有一个磁头，尝试并行读取文件只会造成磁头频繁抖动，反而降低IO效率。而对于固态硬盘，虽然的确存在多个并行IO通道，但是对于服务器并行处理的多个请求而言，硬盘已经在做并行IO了，对单个请求采用并行IO无异于拆东墙补西墙。因此，正确的做法不是改用并行IO，而是一边读取文件一边输出响应，把响应输出时机提前至读取第一个文件的时刻。</p>
      <p class="detialInfo">按上述方式解决第一个问题后，因为服务器不需要完整地缓存每个请求的输出数据了，第二个问题也迎刃而解。</p>
      <p class="detialInfo">2、实现——根据以上设计，第二版代码按以下方式调整了部分函数:</p>
      <pre><code class="javascript">function main(argv){<br>&nbsp;var config = JSON.parse(fs.readFileSync(argv[0],'utf-8')),<br>&nbsp;root = config.root || '.',<br>&nbsp;port = config.port || 80;<br>&nbsp;http.createServer(function(request,response){<br>&nbsp;&nbsp;var urlInfo = parseURL(root,request.url);<br>&nbsp;&nbsp;validataFile(urlInfo.pathnames,function(err,pathnames){<br>&nbsp;&nbsp;&nbsp;if(err){<br>&nbsp;&nbsp;&nbsp;&nbsp;response.writeHead(404);<br>&nbsp;&nbsp;&nbsp;&nbsp;response.end(err.message);<br>&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;response.writeHead(200,{'Content-Type':urlInfo.mime});<br>&nbsp;&nbsp;&nbsp;&nbsp;outputFiles(pathnames,response);<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;});<br>&nbsp;}).listen(port);<br>}<br><br>function outputFiles(pathnames,writer){<br>&nbsp;(function next(i,len){<br>&nbsp;&nbsp;if(i < len){<br>&nbsp;&nbsp;&nbsp;var reader = fs.createReadStream(pathnames[i]);<br>&nbsp;&nbsp;&nbsp;reader.pipe(writer,{end:false});<br>&nbsp;&nbsp;&nbsp;reader.on('end',function(){<br>&nbsp;&nbsp;&nbsp;&nbsp;next(i + 1,len);<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}else(writer.end();}<br>&nbsp;}(0,pathnames.length));<br>}<br><br>function validataFiles(pathnames,callback){<br>&nbsp;(function next(i,len){<br>&nbsp;&nbsp;if(i < len){<br>&nbsp;&nbsp;&nbsp;fs.stat(pathnames[i],function(err,stats){<br>&nbsp;&nbsp;&nbsp;&nbsp;if(err){ callback(err);}<br>&nbsp;&nbsp;&nbsp;&nbsp;else if(!stats.isFile()){callback(new Error));}<br>&nbsp;&nbsp;&nbsp;&nbsp;else{ next( i + 1, len);}<br>&nbsp;&nbsp;&nbsp;});<br>&nbsp;&nbsp;}else{callback(null,pathnames);}<br>&nbsp;}(0,pathnames.length));<br>}</code></pre>
      <p class="detialInfo">第二版代码在检查了请求的所有文件是否有效之后，立即就输出了响应头，并接着一边按顺序读取文件一边输出响应内容。并且，在读取文件时，第二版代码直接使用了只读数据流来简化代码。</p>
      <h2 class="subtitle">第三次迭代</h2>
      <p class="detialInfo">第二次迭代之后，服务器本身的功能和性能已经得到了初步满足。接下来我们需要从稳定性的角度重新审视一下代码，看看还需要做些什么。</p>
      <p class="detialInfo">1、设计——从工程角度上讲，没有绝对可靠的系统。即使第二次迭代的代码经过反复检查后能确保没有bug，也很难说是否会因为NodeJS本身，或者是操作系统本身，甚至是硬件本身导致我们的服务器程序在某一天挂掉。因此一般生产环境下的服务器程序都配有一个守护进程，在服务挂掉的时候立即重启服务。一般守护进程的代码会远比服务进程的代码简单，从概率上可以保证守护进程更难挂掉。如果再做得严谨一些，甚至守护进程自身可以在自己挂掉时重启自己，从而实现双保险。</p>
      <p class="detialInfo">因此在本次迭代时，我们先利用NodeJS的进程管理机制，将守护进程作为父进程，将服务器程序作为子进程，并让父进程监控子进程的运行状态，在其异常退出时重启子进程。</p>
      <p class="detialInfo">2、实现——根据以上设计，我们编写了守护进程需要的代码:</p>
      <pre><code class="javascript">var cp = require('child_process');<br>var worker;<br><br>function spawn(server,config){<br>&nbsp;worker = cp.spawn('node',[server,config]);<br>&nbsp;worker.on('exit',function(code){<br>&nbsp;&nbsp;if(code !== 0){spawn(server,config);}<br>&nbsp;});<br>}<br><br>function main(argv){<br>&nbsp;spawn('server.js',argv[0]);<br>&nbsp;process.on('SIGIERM',function(){<br>&nbsp;&nbsp;worker.kill();<br>&nbsp;&nbsp;process.exit(0);<br>&nbsp;});<br>}<br><br>main(process.argv.slice(2));<br><br>/*服务器代码本身的入口函数也要做以下调整*/<br>function main(argv){<br>&nbsp;var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),<br>&nbsp;root = config.root || '.',<br>&nbsp;port = config.port || 80,server;<br>&nbsp;server = http.createServer(function(request, response){<br>&nbsp;&nbsp;/*...*/<br>&nbsp;}).listen(port);<br>&nbsp;process.on('SIGTERM', function(){<br>&nbsp;&nbsp;server.close(function(){<br>&nbsp;&nbsp;&nbsp;process.exit(0);<br>&nbsp;&nbsp;});<br>&nbsp;});<br>}</code></pre>
      <p class="detialInfo">我们可以把守护进程的代码保存为daemon.js，之后我们可以通过node daemon.js config.json启动服务，而守护进程会进一步启动和监控服务器进程。此外，为了能够正常终止服务，我们让守护进程在接收到SIGTERM信号时终止服务器进程。而在服务器进程这一端，同样在收到SIGTERM信号时先停掉HTTP服务再正常退出。至此，我们的服务器程序就靠谱很多了。</p>
      <h2 class="subtitle">第四次迭代</h2>
      <p class="detialInfo">在我们解决了服务器本身的功能、性能和可靠性的问题后，接着我们需要考虑一下代码部署的问题，以及服务器控制的问题。</p>
      <p class="detialInfo">程序在服务器上有一个固定的部署目录，每次程序有更新后，都重新发布到部署目录里。而一旦完成部署后，一般也可以通过固定的服务控制脚本启动和停止服务。</p>
      <h2 class="subtitle">后续迭代</h2>
      <p class="detialInfo">我们的服务器程序正式上线工作后，我们接下来或许会发现还有很多可以改进的点。比如服务器程序在合并JS文件时可以自动在JS文件之间插入一个;来避免一些语法问题，比如服务器程序需要提供日志来统计访问量，比如服务器程序需要能充分利用多核CPU，等等。</p>
    </div>
  </div>
  <div class="column sideCol l">
    <ul class="title-list">
      <li><a class="link" href="#content1">NodeJS基础</a></li>
      <li><a class="link" href="#content2">node.js安装</a></li>
      <li><a class="link" href="#content3">安装vue.js</a></li>
      <li><a class="link" href="#content4">代码的组织和部署</a></li>
      <li><a class="link" href="#content5">文件操作</a></li>
      <li><a class="link" href="#content6">网络操作</a></li>
      <li><a class="link" href="#content7">进程管理</a></li>
      <li><a class="link" href="#content8">异步编程</a></li>
      <li><a class="link" href="#content9">大示例</a></li>
    </ul>
  </div>
</div>

<script>
  
</script>